/**
 * @(#)Boat.java	
 * 
 * History:	
 * 1.0	11 May 2012
 * 		Created
 * 2.0	22 May 2012
 * 		Renamed ReceiveRower into ReceivePickSeatMessage
 * 		Added behaviour ReceiveInformCoordinateMessage
 * 3.0	28 May 2012
 * 		Integrated with the GUI classes
 */
package upm.pb4agent.team1.platform;

import jade.content.Concept;
import jade.content.ContentElement;
import jade.content.lang.Codec.CodecException;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.content.onto.basic.Action;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import upm.pb4agent.ontology.PickSeat;
import upm.pb4agent.team1.AgBase1;
import upm.pb4agent.team1.ontology.InformCoordinate;
import upm.pb4agent.team1.team.Constant;
import upm.pb4agent.team1.util.AgentUtil;
import es.decoroso.communicationAgent.bean.Message;
import es.decoroso.communicationAgent.bean.Rower;
import es.decoroso.communicationAgent.bean.Vector2;

/**
 * This class handles the boat functions.
 * 
 * @version 2.0
 * @author Bob Nicolaus
 * 
 */
public class Boat extends AgBase1 {

	private static final long serialVersionUID = -5176914350163088307L;

	private List<AID> rowerList = new ArrayList<AID>();

	private int abscissa = 0;
	private int ordinate = 0;

	private AID refereeAID = null;

	// All attributes below are needed to integrate with the GUI
	/** Actual position of the boat */
	Vector2 position = new Vector2(0f, 0f);

	/** Actual orientation of the boat */
	Vector2 orientation = new Vector2(0f, 0f);

	/** Group number */
	int groupNumber = 0;

	/** Rowers of the boat */
	List<Rower> rowers = new LinkedList<Rower>();

	// Attributes used to calculate the row
	float timer = 3000f;
	float posX, posY;
	float rowX, rowY;

	/** Id of the communication agent */
	private AID comunicationAgent;

	// End

	@Override
	protected void setup() {
		System.out.println(getLocalName() + ": Enters the system");

		Object[] args = getArguments();
		if (args != null) {
			groupNumber = Integer.parseInt((String) args[0]);
		}

		// Integration with GUI
		// SET THE START POSITION
		setStartPosition();

		// FIND THE COMMUNICATION AGENT
		this.comunicationAgent = locateService(this, "COMMUNICATION");

		// ADD THE BEHAVIOUR
		addBehaviour(new NotifyGraphicLayer(this, comunicationAgent));
		// End of integration

		addBehaviour(new ReceivePickSeatMessage(this));
		addBehaviour(new ReceiveInformCoordinateMessage(this));
	}

	// /////////////////////////////////////////////////////
	// ADD THE CODE FROM HERE TO THE END OF THE DOCUMENT //
	// /////////////////////////////////////////////////////
	/**
	 * Call this method after set the group number
	 * 
	 * This methods sets the start position of the boat
	 */
	private void setStartPosition() {
		position.x = groupNumber * 25;

	}

	/**
	 * Start a row
	 * 
	 * This method should be called when a new row it's been received.
	 * 
	 * @param x
	 *            X Value of the row
	 * @param y
	 *            Y Value of the row
	 */
	public void toRow(float x, float y) {
		posX = position.x;
		posY = position.y;

		if (timer < 3000) { // row not finished
			float restX = orientation.x - orientation.x * timer / 3000.0f;
			float restY = orientation.y - orientation.y * timer / 3000.0f;

			orientation.x = restX + x;
			orientation.y = restY + y;
		} else {
			orientation.x = x;
			orientation.y = y;
		}

		timer = 0;
	}

	/**
	 * Update the position of the boat
	 */
	private void updateRow() {
		if (timer <= 3000) {
			position.x = posX + (orientation.x * timer / 3000.0f);
			position.y = posY + (orientation.y * timer / 3000.0f);
		}
	}

	/**
	 * This method is called from NotifyGraphicLayer
	 * 
	 * This method updates the row force and send the information to the graphic
	 * layer
	 * 
	 * @return Message with the actual position of the system
	 */
	public Message createMessage() {
		timer = timer + 50;
		updateRow();
		Message m = new Message(groupNumber, orientation, position, rowers);
		return m;
	}

	/**
	 * This method search for an agent that offers a service we are searching
	 * for
	 * 
	 * @param agentType
	 *            Type of agent you want to locate
	 * 
	 * @return AID of identifyed agent or null if not found
	 */
	private AID locateService(Agent agent, String agentType) {

		AID result = null;

		// Create agent description
		DFAgentDescription dfd = new DFAgentDescription();
		ServiceDescription sd = new ServiceDescription();
		sd.setType(agentType);
		dfd.addServices(sd);

		// Search requested type agents
		DFAgentDescription[] res = new DFAgentDescription[1];
		try {
			res = DFService.search(agent, dfd);
			if (res.length > 0) {
				result = res[0].getName();
			} else {
				// Not agent found
				return null;
			}
		} catch (FIPAException ex) {
			// Not agent found exception
			ex.printStackTrace();
		}
		return result;
	}

	/**
	 * Receives pick seat message from Rower.
	 * 
	 * @author Bob Nicolaus
	 * 
	 */
	class ReceivePickSeatMessage extends SimpleBehaviour {

		private static final long serialVersionUID = 4815008870992963355L;

		public ReceivePickSeatMessage(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			if (rowerList.size() < 4) {
				ACLMessage rowerInformation = receive(MessageTemplate.and(
						MessageTemplate.and(standardTemplate, MessageTemplate
								.MatchPerformative(ACLMessage.INFORM)),
						MessageTemplate
								.MatchProtocol(Constant.PICK_SEAT_PROTOCOL)));

				if (rowerInformation != null) {
					try {

						ContentElement contentElement = getContentManager()
								.extractContent(rowerInformation);

						if (contentElement instanceof Action) {

							Action action = (Action) contentElement;
							Concept concept = action.getAction();

							if (concept instanceof PickSeat) {
								PickSeat pickSeat = (PickSeat) concept;

								String rowerName = rowerInformation.getSender()
										.getLocalName();
								int rowerNumber = Character
										.getNumericValue(rowerName
												.charAt(rowerName.length() - 1));
								int seatNumber = Integer.parseInt(pickSeat
										.getSeatID().getSeatID());

								addRower(rowerNumber, seatNumber);

								System.out.println(getLocalName()
										+ ": "
										+ rowerInformation.getSender()
												.getLocalName() + " in seat "
										+ pickSeat.getSeatID().getSeatID());
							}
						}
					} catch (UngroundedException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					}
				} else {

					block();
				}
			}
		}

		@Override
		public boolean done() {
			return rowerList.size() == 4;
		}

		/**
		 * Add the rower to the rowers list for GUI purpose.
		 * 
		 * @param rowerNumber
		 *            The rower number
		 * @param seatNumber
		 *            The seat number that the rower picks
		 */
		private void addRower(int rowerNumber, int seatNumber) {
			for (Rower rower : rowers) {

				// Check if it already exists in the list
				if (rower.getNumber() == rowerNumber) {
					rowers.remove(rower);
					break;
				}
			}

			rowers.add(new Rower(rowerNumber, seatNumber));
		}
	}

	/**
	 * Receives inform coordinate message from Watcher.
	 * 
	 * @author Dave Rojas
	 * 
	 */
	class ReceiveInformCoordinateMessage extends SimpleBehaviour {

		private static final long serialVersionUID = -321916674778785398L;

		public ReceiveInformCoordinateMessage(Agent agent) {
			super(agent);
		}

		@Override
		public void action() {
			ACLMessage informCoordinateMessage = receive(MessageTemplate
					.and(MessageTemplate.and(standardInternalTemplate,
							MessageTemplate
									.MatchPerformative(ACLMessage.INFORM)),
							MessageTemplate
									.MatchProtocol(Constant.INFORM_COORDINATE_PROTOCOL)));

			if (informCoordinateMessage != null) {
				ContentElement contentElement;
				try {

					contentElement = getContentManager().extractContent(
							informCoordinateMessage);

					if (contentElement instanceof Action) {

						Action action = (Action) contentElement;
						Concept concept = action.getAction();

						if (concept instanceof InformCoordinate) {

							InformCoordinate coordinate = (InformCoordinate) concept;
							ordinate = coordinate.getOrdinate();
							abscissa = coordinate.getAbscissa();

							toRow(abscissa, ordinate);

							// Send to Referee
							if (refereeAID == null) {
								refereeAID = AgentUtil.searchAgentInDF(myAgent,
										Constant.REFEREE_DF_TYPE);
							}

							sendInternalMessage(ACLMessage.INFORM, refereeAID,
									action, Constant.INFORM_COORDINATE_PROTOCOL);
						}
					}
				} catch (UngroundedException e) {
					e.printStackTrace();
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				} catch (FIPAException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {

				block();
			}
		}

		@Override
		public boolean done() {
			return false;
		}

	}
}
